package zdream.pmfield.test.stage.roll;

import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.atomic.AtomicBoolean;

import zdream.pmfield.core.Result;
import zdream.pmfield.data.foe.Foe;
import zdream.pmfield.data.foe.MoveRouteType;
import zdream.pmfield.data.foe.MoveTemplate;
import zdream.pmfield.data.foe.MoveType;
import zdream.pmfield.data.foe.VStat;
import zdream.pmfield.stage.StageBuilderForTest;
import zdream.pmfield.stage.StageWrapper;
import zdream.pmfield.stage.action.AttackAction;
import zdream.pmfield.stage.director.Roll;
import zdream.pmfield.stage.message.ActionRequestMessage;
import zdream.pmfield.stage.message.ActionRequestReply;
import zdream.pmfield.stage.message.ActionStartMessage;
import zdream.pmfield.stage.message.RollReportMessage;
import zdream.pmfield.stage.message.TurnStartMessage;
import zdream.pmfield.stage.object.FoeActor;
import zdream.pmfield.stage.object.StageMove;
import zdream.pmfield.storage.FoeTemplateStorage;
import zdream.pmfield.test.utils.ResourceConstant;
import zdream.pmfield.test.utils.StageConstant;

import static org.junit.Assert.*;

/**
 * @author Zdream
 * @date 2023-12-01
 * @since 0.0.1
 */
public class AccuracyAttenuationForDistanceCase {
	FoeTemplateStorage storage;

	@Before
	public void before() {
		storage = new FoeTemplateStorage();
		storage.load();
		storage.loadMovesInResource(ResourceConstant.RES_PATH_TEST_MOVES_JSON);
	}

	@Test
	public void testAccuracyInAnyDistance() {
		MoveTemplate uselessMove = storage.findMoveById(10000015, 1);
		assertEquals(uselessMove.name, "无效果");
		MoveTemplate normalMove = storage.findMoveById(1, 1);
		assertEquals(normalMove.attackerUsing, MoveType.PHYSICAL);
		final int normalMoveFixed = normalMove.checkRateFixed;

		Foe foe1 = Foe.builder(storage.findFoeById(133))
				.withLevel(20)
				.withMove(normalMove)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(79))
				.withLevel(20)
				.withMove(uselessMove)
				.build();
		StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.buildWrapper();

		System.out.println("TEST 1: 测试在同一个位置发动攻击, 衰减应该是 0");
		AtomicBoolean checked = new AtomicBoolean(false);

		// 默认的距离衰减是 3
		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest(foe2);
		wrapper.onMessage(ActionRequestMessage.class, message -> {
			final FoeActor foeActor = message.foe;
			if (foeActor.foe != foe1) {
				return;
			}
			final FoeActor enemy = wrapper.stage.findFoe(foe2);
			assertNotNull(enemy);

			final int currentX = foeActor.seat.x;
			final int targetX = enemy.seat.x;

			final ActionRequestReply template = message.createReplyTemplate();
			AttackAction action = template.createAttackAction();
			if (currentX == targetX) {
				action.move = foeActor.attackMoves.get(0);
				action.targetFoe = enemy;
				final Result result = message.commit(template);
				assertTrue(result.errorMessage, result.success);
				return;
			}

			final StageMove move = foeActor.findMoveToMove();
			if (!foeActor.hasEnoughActionPointsForMove(move)) {
				action.move = foeActor.findSkipTurnMove();
				final Result result = message.commit(template);
				assertTrue(result.errorMessage, result.success);
				return;
			}

			action.move = move;
			action.targetSeat = (currentX < targetX) ? foeActor.seat.right : foeActor.seat.left;
			final Result result = message.commit(template);
			assertTrue(result.errorMessage, result.success);
		});
		wrapper.onMessage(RollReportMessage.class, reportMessage -> {
			if (!StageConstant.ROLL_TITLE_ACTION.equals(reportMessage.title)) {
				return;
			}
			final FoeActor foeActor = reportMessage.foe;
			if (foeActor.foe != foe1) {
				return;
			}

			final Roll[] rolls = reportMessage.rolls;
			for (Roll roll : rolls) {
				assertEquals(roll.baseValue, foeActor.vStats[VStat.STRENGTH.ordinal()] + normalMoveFixed);
				assertEquals(roll.fixedBaseValue, foeActor.vStats[VStat.STRENGTH.ordinal()] + normalMoveFixed);
			}

			checked.set(true);
		});

		wrapper.runUntil(stage0 -> checked.get());

		System.out.println("TEST 2: 测试在离对方一格的位置发动攻击, 衰减应该是 3");
		AtomicBoolean moved = new AtomicBoolean(false);
		checked.set(false);

		wrapper.clearOnMessageHooks();
		wrapper.onDefaultActionRequest(foe2);
		wrapper.onMessage(ActionRequestMessage.class, message -> {
			final FoeActor foeActor = message.foe;
			if (foeActor.foe != foe1) {
				return;
			}
			final FoeActor enemy = wrapper.stage.findFoe(foe2);
			assertNotNull(enemy);

			final ActionRequestReply template = message.createReplyTemplate();
			AttackAction action = template.createAttackAction();
			if (moved.get()) {
				assertEquals(foeActor.seat.distance(enemy.seat), 1);
				action.move = foeActor.attackMoves.get(0);
				action.targetFoe = enemy;
				final Result result = message.commit(template);
				assertTrue(result.errorMessage, result.success);
				return;
			}

			// 1 点移动力还是有的吧
			action.move = foeActor.findMoveToMove();
			action.targetSeat = foeActor.seat.left;
			final Result result = message.commit(template);
			assertTrue(result.errorMessage, result.success);
			moved.set(true);
		});
		wrapper.onMessage(RollReportMessage.class, reportMessage -> {
			if (!StageConstant.ROLL_TITLE_ACTION.equals(reportMessage.title)) {
				return;
			}
			final FoeActor foeActor = reportMessage.foe;
			if (foeActor.foe != foe1) {
				return;
			}
			if (!moved.get()) {
				return;
			}

			final Roll[] rolls = reportMessage.rolls;
			for (Roll roll : rolls) {
				assertEquals(roll.baseValue, foeActor.vStats[VStat.STRENGTH.ordinal()] + normalMoveFixed);
				assertEquals(roll.fixedBaseValue, foeActor.vStats[VStat.STRENGTH.ordinal()] + normalMoveFixed - 3);
			}

			checked.set(true);
		});

		wrapper.runUntil(stage0 -> checked.get());

		System.out.println("TEST 3: 测试在离对方两格的位置发动攻击, 期望: 太远了打不到");
		moved.set(false);
		checked.set(false);

		wrapper.clearOnMessageHooks();
		wrapper.onDefaultActionRequest(foe2);
		wrapper.onMessage(ActionRequestMessage.class, message -> {
			final FoeActor foeActor = message.foe;
			if (foeActor.foe != foe1) {
				return;
			}
			final FoeActor enemy = wrapper.stage.findFoe(foe2);
			assertNotNull(enemy);

			final ActionRequestReply template = message.createReplyTemplate();
			AttackAction action = template.createAttackAction();
			if (moved.get()) {
				assertEquals(foeActor.seat.distance(enemy.seat), 2);
				action.move = foeActor.attackMoves.get(0);
				action.targetFoe = enemy;
				final Result result = message.commit(template);
				assertFalse(result.success);
				assertNotNull(result.errorMessage);
				assertTrue(result.errorMessage.contains("不在技能的发动范围内"));
				checked.set(true);
				return;
			}

			// 1 点移动力还是有的吧
			action.move = foeActor.findMoveToMove();
			action.targetSeat = foeActor.seat.left;
			final Result result = message.commit(template);
			assertTrue(result.errorMessage, result.success);
			moved.set(true);
		});

		wrapper.runUntil(stage0 -> checked.get());
	}

	@Test
	public void infiltratorMoveWithoutAccuracyPenalty() {
		MoveTemplate uselessMove = storage.findMoveById(10000015, 1);
		assertEquals(uselessMove.name, "无效果");
		MoveTemplate infiltrator = storage.findMoveById(10000201, 1);
		assertEquals(infiltrator.name, "穿透");
		assertEquals(MoveRouteType.INFILTRATOR, infiltrator.target.route);
		assertEquals(0, infiltrator.checkRateFixed);

		Foe foe1 = Foe.builder(storage.findFoeById(133))
				.withLevel(20)
				.withMove(uselessMove)
				.withMove(infiltrator)
				.build();
		Foe foe2 = Foe.builder(storage.findFoeById(79))
				.withLevel(20)
				.withMove(uselessMove)
				.build();
		StageWrapper wrapper = StageBuilderForTest.builder()
				.putFoeToOneSide(foe1)
				.putFoeToAnotherSide(foe2)
				.withStorage(storage)
				.buildWrapper();

		wrapper.printBeforeHandleMessage();
		wrapper.onDefaultActionRequest();
		wrapper.runUntil(TurnStartMessage.class, message -> message.foe.foe == foe1);
		wrapper.onActionRequest(wrapper.createDefaultController().withMove(infiltrator), foe1);
		wrapper.runUntil(ActionStartMessage.class);
		wrapper.runUntil(RollReportMessage.class, message -> {
			assertEquals(StageConstant.ROLL_TITLE_ACTION, message.title);

			final FoeActor foeActor = message.foe;
			assertEquals(foe1, foeActor.foe);

			final Roll[] rolls = message.rolls;
			for (Roll roll : rolls) {
				assertEquals(roll.baseValue, foeActor.getVStat(VStat.AWARENESS));
				assertEquals(roll.fixedBaseValue, foeActor.getVStat(VStat.AWARENESS));
			}
			return true;
		});
	}
}
